﻿using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FreshMeat.Threading;

namespace FreshMeat.Storage
{
   /// <summary>
   /// Context for a <see cref="MemoryStore{TId,TData}"/>.
   /// </summary>
   /// <typeparam name="TId">Type of ID.</typeparam>
   /// <typeparam name="TData">Type of data.</typeparam>
   public class MemoryStoreContext<TId, TData> : StoreContext, IStoreContext<TId, TData>
   {
      readonly Dictionary<TId, string> cache;
      readonly ISerializer<TData> serializer;

      /// <summary>
      /// Creates instance.
      /// </summary>
      /// <param name="cache"></param>
      /// <param name="serializer"></param>
      /// <param name="resourceLock"></param>
      public MemoryStoreContext(Dictionary<TId, string> cache, ISerializer<TData> serializer, ILock resourceLock)
         : base(resourceLock)
      {
         this.cache = cache;
         this.serializer = serializer;
      }

      /// <summary>
      /// Saves data to the store.
      /// </summary>
      /// <param name="id">The ID.</param>
      /// <param name="data">The data.</param>
      /// <returns>True if the item already existed; false if it was created.</returns>
      public bool Save(TId id, TData data)
      {
         Lock();

         return InternalSave(id, data);
      }

      /// <summary>
      /// Saves data to the store.
      /// </summary>
      /// <param name="id">The ID.</param>
      /// <param name="data">The data.</param>
      /// <returns>True if the item already existed; false if it was created.</returns>
      public async Task<bool> SaveAsync(TId id, TData data)
      {
         await LockAsync();

         return InternalSave(id, data);
      }

      bool InternalSave(TId id, TData data)
      {
         var alreadyExists = cache.ContainsKey(id);
         cache[id] = serializer.Serialize(data);
         return alreadyExists;
      }

      /// <summary>
      /// Loads data from the store.
      /// </summary>
      /// <param name="id">The ID.</param>
      /// <param name="data">The data or default{T} if it did not exist.</param>
      /// <returns>True if the data was found.</returns>
      public bool Load(TId id, out TData data)
      {
         Lock();

         return InternalLoad(id, out data);
      }

      /// <summary>
      /// Loads data from the store.
      /// </summary>
      /// <param name="id">The ID.</param>
      /// <returns>Tuple containg true if the data was found and the data or false if not found.</returns>
      public async Task<StoreLoadResult<TData>> LoadAsync(TId id)
      {
         await LockAsync();

         TData data;
         var found = InternalLoad(id, out data);
         return new StoreLoadResult<TData>(found, data);
      }

      bool InternalLoad(TId id, out TData data)
      {
         string raw;
         var exists = cache.TryGetValue(id, out raw);
         data = exists ? serializer.Deserialize(raw) : default(TData);
         return exists;
      }

      /// <summary>
      /// Gets all available data from the store.
      /// </summary>
      /// <returns>All available data; this may be empty if there was no data.</returns>
      public IEnumerable<TData> LoadMany()
      {
         Lock();

         return InternalLoadMany();
      }

      /// <summary>
      /// Gets all available data from the store.
      /// </summary>
      /// <returns>All available data; this may be empty if there was no data.</returns>
      public async Task<IEnumerable<TData>> LoadManyAsync()
      {
         await LockAsync();

         return InternalLoadMany();
      }

      IEnumerable<TData> InternalLoadMany()
      {
         return cache.Values.Select(serializer.Deserialize);
      }

      /// <summary>
      /// Gets all available IDs from the store.
      /// </summary>
      /// <returns>All available IDs; this may be empty if there was no data.</returns>
      public IEnumerable<TId> LoadManyIds()
      {
         Lock();

         return InternalLoadManyIds();
      }

      /// <summary>
      /// Gets all available IDs from the store.
      /// </summary>
      /// <returns>All available IDs; this may be empty if there was no data.</returns>
      public async Task<IEnumerable<TId>> LoadManyIdsAsync()
      {
         await LockAsync();

         return InternalLoadManyIds();
      }

      IEnumerable<TId> InternalLoadManyIds()
      {
         return cache.Keys;
      }

      /// <summary>
      /// Removes data from the store.
      /// </summary>
      /// <param name="id">The ID.</param>
      /// <returns>True if the data existed.</returns>
      public bool Delete(TId id)
      {
         Lock();

         return InternalDelete(id);
      }

      /// <summary>
      /// Removes data from the store.
      /// </summary>
      /// <param name="id">The ID.</param>
      /// <returns>True if the data existed.</returns>
      public async Task<bool> DeleteAsync(TId id)
      {
         await LockAsync();

         return InternalDelete(id);
      }

      bool InternalDelete(TId id)
      {
         return cache.Remove(id);
      }
   }
}